package de.tuhh.simsearch.controller.contentmgmt;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import javax.ejb.EJB;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;

import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.tika.exception.TikaException;
import org.xml.sax.SAXException;

import de.tuhh.simsearch.constants.Constants;
import de.tuhh.simsearch.controller.contentmgmt.interfaces.ContentControllerInterface;
import de.tuhh.simsearch.dto.CategoryDTO;
import de.tuhh.simsearch.dto.FileDTO;
import de.tuhh.simsearch.model.persistence.contentmgmt.eao.interfaces.ContentMgmtEAOInterface;
import de.tuhh.simsearch.model.persistence.contentmgmt.entities.CategoryEntity;
import de.tuhh.simsearch.model.persistence.contentmgmt.entities.FileEntity;
import de.tuhh.simsearch.model.persistence.contentmgmt.files.FileAccess;
import de.tuhh.simsearch.model.persistence.contentmgmt.files.interfaces.FileAccessInterface;
import de.tuhh.simsearch.model.persistence.contentmgmt.mltools.LuceneIndexer;
import de.tuhh.simsearch.model.persistence.contentmgmt.mltools.interfaces.IndexerInterface;
import de.tuhh.simsearch.util.ContentConversion;

/**
 * The content controller manages files, the metadata of files, categories. The controller also manages search requests
 * for content and categories.
 * 
 * @author sim-search
 * 
 */
@Stateless
@LocalBean
public class ContentController implements ContentControllerInterface {

  /**
   * The contentMgmt bean used to access the conmgmt.
   */
  @EJB private ContentMgmtEAOInterface conMmgmt;

  @EJB private ContentConversion conv;

  private FileAccessInterface fileaccess = new FileAccess();

  private IndexerInterface indexer;

  /**
   * The default ContentController. Generates a new Indexer.
   */
  public ContentController() {

    indexer = new LuceneIndexer();
  }

  @Override
  public FileDTO uploadFile(FileDTO fdto, InputStream is) {

    FileEntity entity = null;
    FileDTO result = null;
    boolean success = true;

    FilePath fp = generateFilePath(fdto);
    if (conMmgmt.isFileNameFree(fp.getFilename(), validatePath(fp.getPath()))) {
      entity = new FileEntity(fp.getFilename(), validatePath(fp.getPath()));
      entity.setDisplayedFilename(fdto.getDisplayedFilename());
      entity.setContentID(Constants.descContentIdMap.get(fdto.getContentType()));
      entity.setOwner(fdto.getOwner());
      entity.setDescription(fdto.getDescription());
      Integer id = Constants.descContentIdMap.get(fdto.getContentType());
      CategoryEntity catEnt = null;

      success = conMmgmt.addFileEntity(entity);
      if (success) {
        
        if (id != null) {
          if (fdto.getCategories() != null && !fdto.getCategories().isEmpty()) {
            for (String category : fdto.getCategories()) {

              catEnt = conMmgmt.getCategoryEntity(category, id);
              if (catEnt != null) {
                entity.addCategory(catEnt);
              } else {
                catEnt = conMmgmt.getCategoryEntity("default", id);
                entity.addCategory(catEnt);
              }
            }
          }
        }
        try {
          fileaccess.saveFile(is, entity);
        } catch (IOException e) {
          success = false;
          e.printStackTrace();
        }
      }
    } else {
      success = false;
    }
    System.out.println("test10");
    try {
      indexer.createIndexForFile(entity);
    } catch (IOException | TikaException | SAXException | NullPointerException e1) {
      
      List<CategoryEntity> tmpc = new ArrayList<CategoryEntity>();
      tmpc.addAll(entity.getCategories());
      for(CategoryEntity c : tmpc) {
        entity.removeCategory(c);
      }
      fileaccess.removeFile(entity);
      conMmgmt.removeFileEntity(entity.getId());
      success = false;
      
      System.out.println("Index could not be created!");
      e1.printStackTrace();
    }
    if (success) {
      try {
        result = conv.fromEntity(entity);
      } catch (NullPointerException e) {
        result = null;
        e.printStackTrace();
      }
    }
    return result;
  }

  @Override
  public InputStream downloadFile(FileDTO fdto) {

    return downloadFile(fdto.getId());
  }

  @Override
  public InputStream downloadFile(BigInteger id) {

    InputStream downloadStream = null;

    FileEntity entity = conMmgmt.getFileEntity(id);

    if (entity != null) {
      try {
        downloadStream = fileaccess.getFile(entity);
      } catch (IOException e) {
        e.printStackTrace();
        downloadStream = null;
      }
    }

    return downloadStream;
  }

  @Override
  public List<FileDTO> searchFiles(String srchstrg, String[] fields, boolean indexing) {

    List<FileDTO> result = null;
    List<BigInteger> fileIds = null;

    if (indexing) {

      try {
        fileIds = indexer.searchInIndex(srchstrg, fields);
      } catch (IOException | ParseException | NullPointerException e) {
        System.out.println("Index could not be searched!");
        result = searchFiles(srchstrg, fields, false);
        e.printStackTrace();
      }
      result = getDtoListFromIds(fileIds);

    } else {

      for (int i = 0; i < fields.length; i++) {
        switch (fields[i]) {
          case LuceneIndexer.CATEGORY_FIELD:
            result = searchFilesInCategoriesNoIndexing(srchstrg);
            break;
          case LuceneIndexer.DESCRIPTION_FIELD:
            result = searchFilesInDescriptionNoIndexing(srchstrg);
            break;
          default:
            result = searchFilesInName(srchstrg);
        }
      }
    }

    return result;
  }

  @Override
  public List<CategoryDTO> searchCategories(String srchStrg) {

    List<CategoryDTO> lCatDto = new ArrayList<CategoryDTO>();

    List<CategoryEntity> lCatEnt = conMmgmt.searchCategories(srchStrg);

    if (lCatEnt != null) {

      for (CategoryEntity ce : lCatEnt) {
        lCatDto.add(conv.fromEntity(ce));
      }
    }

    return lCatDto;
  }

  @Override
  public boolean addCategory(CategoryDTO cdto) {

    boolean retVal = false;

    CategoryEntity cEntity = conv.fromDto(cdto);

    if (cEntity != null) {
      if (conMmgmt.addCategoryEntity(cEntity)) {

        retVal = true;
      }
    }

    return retVal;
  }

  @Override
  public boolean changeCategory(String oldcategoryName, Integer oldcontentID, String newcategoryName,
      Integer newcontentID) {

    boolean success = false;

    if (conMmgmt.getCategoryEntity(oldcategoryName, oldcontentID) != null) {
      success = conMmgmt.updateCategoryEntity(oldcategoryName, oldcontentID, newcategoryName, newcontentID);
    }

    return success;
  }

  @Override
  public CategoryDTO getCategory(String categoryName, String contentType) {

    CategoryEntity cEntity = null;
    CategoryDTO cDto = null;
    Integer contentID = null;

    contentID = Constants.descContentIdMap.get(contentType);

    if (contentID != null) {

      cEntity = conMmgmt.getCategoryEntity(categoryName, contentID);

      if (cEntity != null) {

        cDto = conv.fromEntity(cEntity);
      }
    }

    return cDto;
  }

  @Override
  public FileDTO getFileInfo(BigInteger id) {

    FileDTO fdto = null;
    FileEntity entity = conMmgmt.getFileEntity(id);

    if (entity != null) {
      fdto = conv.fromEntity(entity);
    }

    return fdto;
  }

  @Override
  public boolean removeFile(BigInteger id) {

    boolean success = false;
    FileEntity entity = conMmgmt.getFileEntity(id);
    success = fileaccess.removeFile(entity);

    if (success) {
      try {
        indexer.deleteIndexForFile(entity);
      } catch (IOException e) {
        System.out.println("Index for file " + entity.getFileName() + " could not be removed");
        e.printStackTrace();
      }
      success = conMmgmt.removeFileEntity(id);

    }
    return success;
  }

  @Override
  public boolean removeCategory(String categoryName, Integer contentID) {

    return conMmgmt.removeCategoryEntity(categoryName, contentID);
  }

  private List<FileDTO> searchFilesInName(String srchstrg) {

    List<FileEntity> filesList = conMmgmt.searchFiles(srchstrg);
    List<FileDTO> filesDtoList = new LinkedList<FileDTO>();

    for (FileEntity e : filesList) {

      filesDtoList.add(conv.fromEntity(e));
    }
    return filesDtoList;
  }

  private List<FileDTO> searchFilesInDescriptionNoIndexing(String srchstrg) {

    List<FileEntity> eList = conMmgmt.searchFilesByDesc(srchstrg);
    List<FileDTO> dList = new ArrayList<FileDTO>();

    if (eList != null && !eList.isEmpty()) {

      for (FileEntity e : eList) {

        dList.add(conv.fromEntity(e));
      }
    }
    return dList;
  }

  private List<FileDTO> searchFilesInCategoriesNoIndexing(String srchstrg) {

    List<FileDTO> dResult = new ArrayList<FileDTO>();
    List<FileEntity> eResult = new ArrayList<FileEntity>();

    List<CategoryEntity> cEntities = conMmgmt.searchCategories(srchstrg);

    if (cEntities != null && !cEntities.isEmpty()) {

      eResult = conMmgmt.searchFiles(cEntities);

      for (FileEntity e : eResult) {

        dResult.add(conv.fromEntity(e));
      }
    }

    return dResult;
  }

  private List<FileDTO> getDtoListFromIds(List<BigInteger> fileIds) {

    List<FileDTO> result = new ArrayList<FileDTO>();
    FileEntity entity;
    FileDTO dto;
    
    if(fileIds != null) {
      for (BigInteger id : fileIds) {
        entity = conMmgmt.getFileEntity(id);
        if (entity != null) {
          dto = conv.fromEntity(entity);
          result.add(dto);
        }
      }
    }

    return result;
  }

  private String validatePath(String path) {

    if (!path.endsWith("/")) {
      path.concat("/");
    }

    return path;
  }

  private FilePath generateFilePath(FileDTO fdto) {

    FilePath ffp = new FilePath();
    long time = System.currentTimeMillis();
    ffp.setFilename(Long.toString(time) + "_" + fdto.getDisplayedFilename());
    ffp.setPath("simSearchFiles/" + fdto.getOwner() + "/");

    return ffp;
  }

  private class FilePath {

    private String filename;
    private String path;

    public String getFilename() {

      return filename;
    }

    public void setFilename(String filename) {

      this.filename = filename;
    }

    public String getPath() {

      return path;
    }

    public void setPath(String path) {

      this.path = path;
    }

  };

}
